﻿using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;

namespace DanMuHelper.Editor
{
    internal static partial class CodeGenerator
    {
        private static List<string> GetAllScenes()
        {
            var scenes = new List<string>();
            var sceneGuids = AssetDatabase.FindAssets("t:Scene");
            foreach (var guid in sceneGuids)
            {
                var path = AssetDatabase.GUIDToAssetPath(guid);
                if (!string.IsNullOrEmpty(path))
                {
                    scenes.Add(path);
                }
                else
                {
                    Debug.LogError($"GUIDToAssetPath failed: {guid}");
                }
            }
            return scenes;
        }

        private static List<string> GetAllResourcesDirectory()
        {
            var resourcesDirectories = new List<string>();
            var resourcesGuids = AssetDatabase.FindAssets("t:DefaultAsset");
            foreach (var guid in resourcesGuids)
            {
                var path = AssetDatabase.GUIDToAssetPath(guid);
                if (!string.IsNullOrEmpty(path))
                {
                    if (path.EndsWith("/Resources"))
                    {
                        resourcesDirectories.Add(path);
                    }
                }
                else
                {
                    Debug.LogError($"GUIDToAssetPath failed: {guid}");
                }
            }
            return resourcesDirectories;
        }

        private class AssetMenuInfo
        {
            public IAssetMenuFilter.AssetType assetType;
            public string assetPath;
            public string menuItemName;
            public string[] codelines;
        }

        [MenuItem(MenuName + "Asset Menu")]
        private static void RegenerateAssetMenus()
        {
            // first find all types implementing IAssetMenuFilter
            var filterTypes = System
                .AppDomain.CurrentDomain.GetAssemblies()
                .Where(a => !a.IsDynamic && !IsReadOnlyAssembly(a))
                .SelectMany(a => a.GetTypes())
                .Where(t => typeof(IAssetMenuFilter).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract);
            IAssetMenuFilter[] filters = filterTypes
                .Select(t => (IAssetMenuFilter)System.Activator.CreateInstance(t))
                .ToArray();
            if (filters.Length == 0)
            {
                var choice = EditorUtility.DisplayDialog(
                    "Asset Menu Filter",
                    "未找到过滤器IAssetMenuFilter",
                    "生成过滤器",
                    "忽略并继续"
                );
                if (choice)
                {
                    RegenerateAssetMenusFilter();
                    return;
                }
            }

            var sceneList = GetAllScenes();
            var resourcesDirectories = GetAllResourcesDirectory();
            var sceneMenus = new List<AssetMenuInfo>();
            var sceneMenuMap = GetSceneMenuNameMap(sceneList.ToArray());
            var topMenuName = "🛣️ 导航/";
            var topSceneMenuName = topMenuName + "场景/";
            for (var i = 0; i < sceneList.Count; i++)
            {
                var scene = sceneList[i];
                string menuItemName = sceneMenuMap[scene];
                // apply filters
                foreach (var filter in filters)
                {
                    menuItemName = filter.Filter(IAssetMenuFilter.AssetType.Scene, scene, menuItemName);
                    if (string.IsNullOrEmpty(menuItemName))
                    {
                        break;
                    }
                }
                if (!string.IsNullOrEmpty(menuItemName))
                {
                    sceneMenus.Add(
                        new AssetMenuInfo()
                        {
                            assetType = IAssetMenuFilter.AssetType.Scene,
                            assetPath = scene,
                            menuItemName = menuItemName,
                            codelines = new string[]
                            {
                                $"[MenuItem(\"{topSceneMenuName + menuItemName}\")]",
                                $"private static void OpenScene_{i}()",
                                "{",
                                "    EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo();",
                                $"    EditorSceneManager.OpenScene(\"{scene}\");",
                                "}",
                            }
                        }
                    );
                }
            }
            var resourcesMenuMap = GetResourcesMenuNameMap(resourcesDirectories.ToArray());
            var topResourcesMenuName = topMenuName + "Resources/";
            var resourcesMenus = new List<AssetMenuInfo>();
            for (var i = 0; i < resourcesDirectories.Count; i++)
            {
                var dir = resourcesDirectories[i];
                var menuItemName = resourcesMenuMap[dir];
                // apply filters
                foreach (var filter in filters)
                {
                    menuItemName = filter.Filter(IAssetMenuFilter.AssetType.ResourcesDirectory, dir, menuItemName);
                    if (string.IsNullOrEmpty(menuItemName))
                    {
                        break;
                    }
                }
                if (!string.IsNullOrEmpty(menuItemName))
                {
                    resourcesMenus.Add(
                        new AssetMenuInfo()
                        {
                            assetType = IAssetMenuFilter.AssetType.ResourcesDirectory,
                            assetPath = dir,
                            menuItemName = menuItemName,
                            codelines = new string[]
                            {
                                $"[MenuItem(\"{topResourcesMenuName + menuItemName}\")]",
                                $"private static void OpenResources_{i}()",
                                "{",
                                $"    // EditorUtility.RevealInFinder(\"{dir}\");",
                                $"    EditorUtility.FocusProjectWindow();",
                                $"    Selection.activeObject = AssetDatabase.LoadAssetAtPath(\"{dir}\", typeof(UnityEngine.Object) );",
                                "}",
                            }
                        }
                    );
                }
            }

            // menu 按 assetPath 排序
            sceneMenus.Sort((a, b) => a.assetPath.CompareTo(b.assetPath));
            resourcesMenus.Sort((a, b) => a.assetPath.CompareTo(b.assetPath));

            if (sceneMenus.Count > 20)
            {
                Debug.LogError("Too many menus ...\n" + string.Join("\n", sceneMenus.Select(s => s.assetPath)));
                EditorUtility.DisplayDialog("Asset Menu", $"菜单数量太多了, 请修改过滤器", "OK");
                return;
            }
            if (resourcesMenus.Count > 20)
            {
                Debug.LogError("Too many menus ...\n" + string.Join("\n", resourcesMenus.Select(s => s.assetPath)));
                EditorUtility.DisplayDialog("Asset Menu", $"菜单数量太多了, 请修改过滤器", "OK");
                return;
            }

            var code = new List<string>();
            foreach (var menu in sceneMenus)
            {
                code.AddRange(menu.codelines);
                code.Add("\n");
            }
            //Debug.Log(string.Join("\n", code));

            foreach (var menu in resourcesMenus)
            {
                code.AddRange(menu.codelines);
                code.Add("\n");
            }

            var codeDir = Path.Combine(Application.dataPath, "DanMuHelper.Generated/Editor");
            if (!Directory.Exists(codeDir))
            {
                Directory.CreateDirectory(codeDir);
            }
            var codeFile = Path.Combine(codeDir, "AssetMenu.cs");
            var tmpl =
                @"// Generated.

using UnityEditor;
using UnityEditor.SceneManagement;

namespace DanMuHelper.Generated
{
    internal static class AssetMenu
    {
#CODE#
    }
}
";
            tmpl = tmpl.Trim();
            var indent = new string(' ', 8);
            var indentedCode = code.Select(c => indent + c);
            tmpl = tmpl.Replace("#CODE#", string.Join("\n", indentedCode));
            if (tmpl.Contains("\r\n"))
            {
                tmpl = tmpl.Replace("\r\n", "\n");
            }
            File.WriteAllText(codeFile, tmpl);
            AssetDatabase.Refresh();
        }

        [MenuItem(MenuName + "Asset Menu - 定制过滤器")]
        private static void RegenerateAssetMenusFilter()
        {
            var filterTypes = System
                .AppDomain.CurrentDomain.GetAssemblies()
                .Where(a => !a.IsDynamic && !IsReadOnlyAssembly(a))
                .SelectMany(a => a.GetTypes())
                .Where(t => typeof(IAssetMenuFilter).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract);
            IAssetMenuFilter[] filters = filterTypes
                .Select(t => (IAssetMenuFilter)System.Activator.CreateInstance(t))
                .ToArray();
            if (filters.Length != 0)
            {
                EditorUtility.DisplayDialog(
                    "Asset Menu Filter",
                    $"Asset Menu Filter: {filters[0]} filters found.",
                    "OK"
                );
                return;
            }
            var dir = Path.Combine(Application.dataPath, "DanMuHelper.Generated/Editor");
            if (!Directory.Exists(dir))
            {
                Directory.CreateDirectory(dir);
            }
            var path = Path.Combine(dir, "AssetMenuFilter.cs");
            var tmpl =
                @"// Generated.

using DanMuHelper.Editor;

namespace DanMuHelper.Generated
{
    public class AssetMenuFilter : IAssetMenuFilter
    {
        private static string[] _excludeScenes = new string[] { ""DOTweenPro Examples"", ""TextMesh Pro"" };

        public string Filter(IAssetMenuFilter.AssetType assetType, string assetPath, string menuItemName)
        {
            if (assetType == IAssetMenuFilter.AssetType.Scene)
            {
                foreach (var scene in _excludeScenes)
                {
                    if (assetPath.Contains(scene))
                    {
                        // 返回null排除菜单
                        return null;
                    }
                }
            }
            // 菜单名称可以原样返回, 也可以自定义
            return menuItemName;
        }
    }
}
";
            tmpl = tmpl.Trim();
            File.WriteAllText(path, tmpl);
            AssetDatabase.Refresh();
        }

        /// <summary>
        /// 生成自动菜单名称
        /// </summary>
        private static Dictionary<string, string> GetSceneMenuNameMap(string[] sceneList)
        {
            var map = new Dictionary<string, string>();
            // 场景菜单, 优先使用场景文件名, 如果场景文件名重复, 则使用全路径
            foreach (var scene in sceneList)
            {
                var sceneName = System.IO.Path.GetFileNameWithoutExtension(scene);
                if (!map.ContainsKey(sceneName))
                {
                    map.Add(scene, sceneName);
                }
                else
                {
                    map.Add(scene, scene);
                }
            }
            return map;
        }

        private static Dictionary<string, string> GetResourcesMenuNameMap(string[] resourcesDirectories)
        {
            var map = new Dictionary<string, string>();
            // 资源菜单, 优先使用目录名, 如果目录名重复, 则使用全路径
            foreach (var resourcesDirectory in resourcesDirectories)
            {
                var parts = resourcesDirectory.Split('/');
                // 掐头去尾
                var menuName = string.Join("/", parts, 1, parts.Length - 2);
                if (menuName == "")
                {
                    menuName = "[Top]";
                }
                if (!map.ContainsKey(resourcesDirectory))
                {
                    map.Add(resourcesDirectory, menuName);
                }
                else
                {
                    map.Add(resourcesDirectory, resourcesDirectory);
                }
            }
            return map;
        }

        private static readonly string[] AssemblyPrefixFilter = new string[]
        {
            "Unity",
            "mscorlib",
            "System",
            "Mono",
            "netstandard",
            "Microsoft",
            "DOTween",
            "JetBrains",
            "PlayerBuildProgramLibrary",
            "DemiLib",
            "DemiEditor",
            "Bee.BeeDriver",
            "unityplastic",
            "nunit.framework",
            "WebGLPlayerBuildProgram",
            "log4net",
            "Python.Runtime",
            "ExCSS.Unity",
            // TODO: lich: 持续补充其他插件程序集前缀
        };

        //private static Regex GetAssemblyPrefixFilterRegex()
        //{
        //    //return new Regex("^(" + string.Join("|", AssemblyPrefixFilter) + ")");
        //    var opt =
        //        @"^(Unity|mscorlib|System|Mono|netstandard|Microsoft|DOTwe{2}n|JetBrains|PlayerBuildProgramLibrary|DemiLib|DemiEditor|Be{2}\.Be{2}Driver|unityplastic|nunit\.framework|WebGLPlayerBuildProgram|log4net|Python\.Runtime|ExCS{2}\.Unity)";
        //}

        private static bool IsReadOnlyAssembly(Assembly assembly)
        {
            var name = assembly.GetName().Name;
            for (int n = AssemblyPrefixFilter.Length, i = 0; i < n; i++)
            {
                if (name.StartsWith(AssemblyPrefixFilter[i]))
                {
                    return true;
                }
            }
            return false;
        }

        // TODO: lich: 文件夹整理, 例如识别类型比较单一的文件夹(Prefab文件夹, 图片文件夹)

        //[MenuItem("Tools/Test get scenes")]
        //private static void TestGetScenes()
        //{
        //    var scenes = GetAllScenes();
        //    Debug.Log("Scenes:");
        //    foreach (var scene in scenes)
        //    {
        //        Debug.Log(scene);
        //    }
        //    var resourcesDirectories = GetAllResourcesDirectory();
        //    Debug.Log("Resources:");
        //    foreach (var resourcesDirectory in resourcesDirectories)
        //    {
        //        Debug.Log(resourcesDirectory);
        //    }

        //    Debug.Log("All Assembly:");
        //    foreach (var assembly in System.AppDomain.CurrentDomain.GetAssemblies().Where(a => !a.IsDynamic))
        //    {
        //        if (IsReadOnlyAssembly(assembly))
        //        {
        //            continue;
        //        }
        //        Debug.Log(assembly.GetName().Name);
        //    }
        //}
    }
}
